﻿using System;
using System.IO;
using System.Net.FtpClient;

namespace System.Net.FtpClient
{
    public static class FtpClientExtensions
    {

        public static void Download(this FtpClient ftp, string remoteFilePath, string localFilePath)
        {
            using (var fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                // istream.Position is incremented accordingly to the reads you perform
                // istream.Length == file size if the server supports getting the file size
                // also note that file size for the same file can vary between ASCII and Binary
                // modes and some servers won't even give a file size for ASCII files! It is
                // recommended that you stick with Binary and worry about character encodings
                // on your end of the connection.
                var buffer = new byte[8192];
                var offset = 0;
                var remoteStream = ftp.OpenRead(remoteFilePath);
                try
                {
                    while (offset < remoteStream.Length)
                    {
                        try
                        {
                            int len;
                            while ((len = remoteStream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                fs.Write(buffer, 0, len);
                                Console.WriteLine("{0:n0}/{1:n0}", remoteStream.Position, remoteStream.Length);
                                offset += len;
                            }
                        }
                        catch (IOException ex)
                        {
                            if (ex.InnerException != null)
                            {
                                var iex = ex.InnerException as System.Net.Sockets.SocketException;
                                if (iex != null && iex.ErrorCode == 10054)
                                {
                                    remoteStream.Close();
                                    remoteStream = ftp.OpenRead(remoteFilePath, restart: offset);
                                }
                                else throw;
                            }
                            else throw;
                        }
                    }
                }
                finally
                {
                    remoteStream.Close();
                }
            }
        }

        public static void Upload(this FtpClient ftp, string localFilePath, string remoteFilePath)
        {
            using (var fs = new FileStream(localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                Upload(ftp: ftp, stream: fs, remoteFilePath: remoteFilePath);
            }
        }

        public static void Upload(this FtpClient ftp, Stream stream, string remoteFilePath)
        {
            var buffer = new byte[8192];
            var offset = 0;
            var remoteStream = ftp.OpenWrite(remoteFilePath);
            try
            {
                while (offset < stream.Length)
                {
                    try
                    {
                        int len;
                        while ((len = stream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            remoteStream.Write(buffer, 0, len);
                            
                            offset += len;
                        }
                    }
                    catch (IOException ex)
                    {
                        if (ex.InnerException != null)
                        {
                            var iex = ex.InnerException as System.Net.Sockets.SocketException;
                            if (iex != null && iex.ErrorCode == 10054)
                            {
                                remoteStream.Close();
                                remoteStream = ftp.OpenAppend(remoteFilePath);
                                remoteStream.Position = offset;
                            }
                            else throw;
                        }
                        else throw;
                    }
                }
            }
            finally
            {
                remoteStream.Close();
            }
        }
    }
}
